package org.samoji.sji.parser;

import java.io.IOException;
import java.io.Reader;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import org.samoji.sji.model.AssignExpr;
import org.samoji.sji.model.BinExpr;
import org.samoji.sji.model.Expr;
import org.samoji.sji.model.ExprSeq;
import org.samoji.sji.model.Id;
import org.samoji.sji.model.Num;
import org.samoji.sji.model.UnExpr;
import org.samoji.sji.profTokenizer.Token.Type;
import org.samoji.sji.profTokenizer.Tokenizer;

public class Parser {
	private Tokenizer tokenizer;
	
	public Parser(Reader in) {
		tokenizer = new Tokenizer(in);
	}
	
	public Expr parse() throws IOException, ParseException {
		tokenizer.next();
		Expr expr = parseSeqExpr();
	
		if (checkType(Type.Eos))
			return expr;
		
		throw new ParseException("No EOS after seqExpr");
	}

	private Expr parseSeqExpr() throws IOException, ParseException {
		List<Expr> exprs = new ArrayList<Expr>();
		
		while (checkType(Type.Semicolon)) {
			tokenizer.next();
		}
		
		exprs.add(parseAssignExpr());
		while (checkType(Type.Semicolon)) {
			tokenizer.next();
			if (!followsAssignExpr())
				exprs.add(parseAssignExpr());
		}
		
		return (exprs.size() == 1) ? exprs.get(0) : new ExprSeq(exprs.toArray(new Expr[exprs.size()]));
	}

	private boolean followsAssignExpr() throws IOException {
		switch(tokenizer.token().type()) {
		case Semicolon:
		case Eos:
		case CloseParen:
			return true;
		default:
			return false;
		}
	}

	private Expr parseAssignExpr() throws IOException, ParseException {
		if (checkType(Type.Id)) {
			String name = tokenizer.token().name();
			tokenizer.next();
			
			if (isAssignOp()) {
				Type operation = tokenizer.token().type();
				tokenizer.next();

				return new AssignExpr(operation, new Id(name), parseAssignExpr());
			}
			tokenizer.prev();
		}
		return parseAddExpr();
	}

	private boolean isAssignOp() {
		switch(tokenizer.token().type()) {
		case MinusBecomes:
		case TimesBecomes:
		case DivBecomes:
		case Becomes:
		case PlusBecomes:
			return true;
		default:
			return false;
		}
	}

	private Expr parseAddExpr() throws IOException, ParseException {
		Expr left = parseMulExpr();
		Expr right;
		
		while (isAddOp()) {
			Type operation = tokenizer.token().type();
			tokenizer.next();
			
			right = parseMulExpr();
			
			left = new BinExpr(operation, left, right);
		}
		return left;
	}
	
	private boolean isAddOp() {
		switch(tokenizer.token().type()) {
		case Plus:
		case Minus:
			return true;
		default:
			return false;
		}
	}

	private Expr parseMulExpr() throws IOException, ParseException {
		Expr left = parseUnaryExpr();
		Expr right;
		
		while (isMulOp()) {
			Type operation = tokenizer.token().type();
			tokenizer.next();
			
			right = parseUnaryExpr();
			
			left = new BinExpr(operation, left, right);
		}
		return left;
	}

	private boolean isMulOp() {
		switch(tokenizer.token().type()) {
		case Times:
		case Div:
			return true;
		default:
			return false;
		}
	}

	private Expr parseUnaryExpr() throws IOException, ParseException {
		if (isAddOp()) {
			Type operation = tokenizer.token().type();
			tokenizer.next();
			
			return new UnExpr(operation, parseUnaryExpr());
		} else {
			return parsePrimaryExpr();
		}
	}

	private Expr parsePrimaryExpr() throws IOException, ParseException {
		if (checkType(Type.Id)) {
			String name = tokenizer.token().name();
			tokenizer.next();

			return new Id(name);
		} else if (checkType(Type.Num)) {
			BigDecimal num = tokenizer.token().num();
			tokenizer.next();
			
			return new Num(num);
		} else if(checkType(Type.OpenParen)) {
			tokenizer.next();
			
			Expr seqExpr = parseSeqExpr();
			
			if (checkType(Type.CloseParen)) {
				tokenizer.next();
			
				return seqExpr;
			} else {
				throw new ParseException("Close paren error");
			}
		}
		throw new ParseException("Parsing error tua madre");
	}

	private boolean checkType(Type type) {
		return tokenizer.token().type().equals(type);
	}
}